/*
 *    Copyright © OpenAtom Foundation.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.inspur.edp.cef.api.changeListener;

import com.inspur.edp.cef.api.RefObject;
import com.inspur.edp.cef.entity.accessor.base.AccessorBase;
import com.inspur.edp.cef.entity.accessor.base.AccessorComparer;
import com.inspur.edp.cef.entity.accessor.base.IAccessor;
import com.inspur.edp.cef.entity.accessor.base.OnMultiLangChangingListener;
import com.inspur.edp.cef.entity.accessor.base.OnPropertyChangingListener;
import com.inspur.edp.cef.entity.accessor.dataType.ValueObjAccessor;
import com.inspur.edp.cef.entity.accessor.entity.ChildEntityAccessor;
import com.inspur.edp.cef.entity.accessor.entity.EntityAccessor;
import com.inspur.edp.cef.entity.accessor.entity.IChildAccessor;
import com.inspur.edp.cef.entity.accessor.entity.IRootAccessor;
import com.inspur.edp.cef.entity.accessor.entity.OnChildAddedListener;
import com.inspur.edp.cef.entity.accessor.entity.OnChildRemovedListener;
import com.inspur.edp.cef.entity.accessor.entity.RootEntityAccessor;
import com.inspur.edp.cef.entity.changeset.AbstractModifyChangeDetail;
import com.inspur.edp.cef.entity.changeset.AddChangeDetail;
import com.inspur.edp.cef.entity.changeset.ChangeType;
import com.inspur.edp.cef.entity.changeset.DeleteChangeDetail;
import com.inspur.edp.cef.entity.changeset.IChangeDetail;
import com.inspur.edp.cef.entity.changeset.ModifyChangeDetail;
import com.inspur.edp.cef.entity.changeset.ValueObjModifyChangeDetail;
import com.inspur.edp.cef.entity.entity.ICefData;
import com.inspur.edp.cef.entity.entity.IEntityDataCollection;
import com.inspur.edp.cef.entity.entity.IKey;
import com.inspur.edp.cef.entity.entity.IValueObjData;
import com.inspur.edp.cef.entity.exception.CefExceptionBase;

import java.util.Objects;

public class EntityDataChangeHandler implements OnPropertyChangingListener, OnChildAddedListener,
    OnChildRemovedListener, OnMultiLangChangingListener {

  private IChangeHandlerContext context;

  public EntityDataChangeHandler(IChangeHandlerContext context) {
    this.context = context;
  }

  private boolean privateIsEquivalentValueEffective;

  public final boolean getIsEquivalentValueEffective() {
    return privateIsEquivalentValueEffective;
  }

  public final void setIsEquivalentValueEffective(boolean value) {
    privateIsEquivalentValueEffective = value;
  }

  @Override
  public final void OnPropertyChanging(IAccessor editingData, String propertyName,
      Object propertyValue, Object orgPropertyValue, IAccessor rootData) {
    if (getIsSuspended()) {
      return;
    }
    if (!getIsEquivalentValueEffective() && AccessorComparer.equals(propertyValue, orgPropertyValue)) {
      return;
    }
    AbstractModifyChangeDetail change = getChangToChangeSet(editingData, rootData);
    if(change != null) {
      change.setItem(propertyName, propertyValue);
    }
    //ProcessEntityDefForModify(editingData, propertyName, propertyValue, rootData);
  }

  public final AbstractModifyChangeDetail getChangToChangeSet(IAccessor editingData, IAccessor rootData) {
//		DataValidator.CheckForNullReference(rootData, "rootData");
//		DataValidator.CheckForEmptyString(propertyName, "propertyName");
//		DataValidator.CheckForNullReference(editingData, "editingData");

    java.util.ArrayList<IAccessor> accessorParentDict = new java.util.ArrayList<IAccessor>();
    if (editingData instanceof RootEntityAccessor) {//主表
      return createCurrent(editingData);
    } else if (editingData instanceof ValueObjAccessor) { //Udt
      //此处应该有单元测试EntityData -> EntityData -> Udt -> Udt -> Udt
      ValueObjAccessor data = (ValueObjAccessor)editingData;
      if (rootData != editingData) {
        getParentAccessorsWithoutRoot(data.getBelongBuffer(), accessorParentDict, rootData);
        IChangeDetail parentChange = getOrCreateParentChangeDetail(rootData, accessorParentDict);
        if (parentChange == null) {
          return null;
        }
        switch (parentChange.getChangeType()) {
          case Added:
          case Deleted:
            return null;
          case Modify:
            return updateChangeToParentChangeDetail(data, parentChange);
          default:
            throw new CefExceptionBase();
        }
      } else {
        if (context.getChangeDetail() == null) {
          AbstractModifyChangeDetail change = createModifyChange(rootData);
          context.setChangeDetail(change);
          return change;
        } else {
          switch (context.getChangeDetail().getChangeType()) {
            case Modify:
              return (AbstractModifyChangeDetail) context.getChangeDetail();
            default:
              throw new CefExceptionBase();
          }
        }
      }
    } else if (editingData instanceof AccessorBase){ //子表
      ChildEntityAccessor data = (ChildEntityAccessor)editingData;
      if (data.getParent() != null) {
        getParentAccessorsWithoutRoot(data.getParent(), accessorParentDict, rootData);
        IChangeDetail changeDetail = getOrCreateParentChangeDetail(rootData, accessorParentDict);
        if (changeDetail == null) {
          return null;
        }
        switch (changeDetail.getChangeType()) {
          case Added:
          case Deleted:
            return null;
          case Modify:
            return this.updateChangeToParentChangeDetail(data, changeDetail);
          default:
            throw new CefExceptionBase();
        }
      } else {
        return createCurrent(data);
      }
    } else {
      throw new IllegalArgumentException("editingData");
    }
  }

  private AbstractModifyChangeDetail createCurrent(IAccessor data) {
    if (context.getChangeDetail() == null) {
      AbstractModifyChangeDetail change = createModifyChange(data);
      context.setChangeDetail(change);
      return (AbstractModifyChangeDetail) context.getChangeDetail();
    } else {
      switch (context.getChangeDetail().getChangeType()) {
        case Added:
          return null;
        case Modify:
          return (AbstractModifyChangeDetail) context.getChangeDetail();
        default:
          throw new CefExceptionBase();
      }
    }
  }

  private int suspendCount = 0;

  public boolean getIsSuspended() {
    return suspendCount > 0;
  }

  public final void resume() {
    if (suspendCount == 0) {
      throw new IllegalArgumentException();
    }
    suspendCount--;
  }

  public final void suspend() {
    suspendCount++;
  }

  private static AbstractModifyChangeDetail createModifyChange(IAccessor rootData) {
    if (rootData instanceof IKey) {
      return new ModifyChangeDetail(((IKey) ((rootData instanceof IKey) ? rootData : null)).getID());
    } else {
      ValueObjModifyChangeDetail tempVar = new ValueObjModifyChangeDetail();
      tempVar.setData((IValueObjData) rootData);
      return tempVar;
    }
  }

  @Override
  public final void OnChildRemoved(IChildAccessor removedAccessor,
      IEntityDataCollection belongCollection, IRootAccessor rootAccessor) {
    if (getIsSuspended()) {
      return;
    }

    java.util.ArrayList<IAccessor> accessorParentDict = new java.util.ArrayList<IAccessor>();
    getParentAccessorsWithoutRoot(removedAccessor.getParent(), accessorParentDict, rootAccessor);
    IChangeDetail changeDetail = getOrCreateParentChangeDetail(rootAccessor, accessorParentDict);
    if (changeDetail == null) {
      return;
    }
    switch (changeDetail.getChangeType()) {
      case Added:
      case Deleted:
        return;
      case Modify:
        addRemoveToParentChangeDetail(removedAccessor, changeDetail);
        break;
      default:
        throw new CefExceptionBase();
    }
  }

  private void addRemoveToParentChangeDetail(IChildAccessor removedAccessor,
      IChangeDetail changeDetail) {

    ModifyChangeDetail parentChangeDetail = (ModifyChangeDetail) (
        (changeDetail instanceof ModifyChangeDetail) ? changeDetail : null);

    IChangeDetail childChangeDetail = parentChangeDetail
        .getChildChangeDetail(removedAccessor.getNodeCode(), removedAccessor.getID());
    if (childChangeDetail == null) {
      childChangeDetail = new DeleteChangeDetail(removedAccessor.getID());
      parentChangeDetail.addChildChangeSet(removedAccessor.getNodeCode(), childChangeDetail);
    } else {
      switch (childChangeDetail.getChangeType()) {
        case Added:
          parentChangeDetail
              .removeChildChangeSet(removedAccessor.getNodeCode(), removedAccessor.getID());
          break;
        case Deleted:
          break;
        case Modify:
          parentChangeDetail
              .removeChildChangeSet(removedAccessor.getNodeCode(), removedAccessor.getID());
          childChangeDetail = new DeleteChangeDetail(removedAccessor.getID());
          parentChangeDetail.addChildChangeSet(removedAccessor.getNodeCode(), childChangeDetail);
          break;
        default:
          throw new CefExceptionBase();
      }
    }

  }

  @Override
  public final void OnChildAdded(IChildAccessor addedItem, IEntityDataCollection belongCollection,
      IRootAccessor rootAccessor) {
    if (getIsSuspended()) {
      return;
    }

    java.util.ArrayList<IAccessor> accessorParentDict = new java.util.ArrayList<IAccessor>();
    getParentAccessorsWithoutRoot(addedItem.getParent(), accessorParentDict, rootAccessor);
    IChangeDetail changeDetail = getOrCreateParentChangeDetail(rootAccessor, accessorParentDict);
    if (changeDetail == null) {
      return;
    }
    switch (changeDetail.getChangeType()) {
      case Added:
        return;
      case Deleted:
        throw new CefExceptionBase("当前实体处于删除状态，无法执行添加操作。");
      case Modify:
        addItemToParentChangeDetail(addedItem, changeDetail);
        break;
      default:
        throw new CefExceptionBase();
    }
  }

  private static void addItemToParentChangeDetail(IChildAccessor addedItem,
      IChangeDetail changeDetail) {

    ModifyChangeDetail parentChangeDetail = (ModifyChangeDetail) (
        (changeDetail instanceof ModifyChangeDetail) ? changeDetail : null);

    IChangeDetail childChangeDetail = parentChangeDetail
        .getChildChangeDetail(addedItem.getNodeCode(), addedItem.getID());
    if (childChangeDetail != null) {
      throw new CefExceptionBase();
    }
    childChangeDetail = new AddChangeDetail(addedItem);
    parentChangeDetail.addChildChangeSet(addedItem.getNodeCode(), childChangeDetail);
  }

  private void getParentAccessorsWithoutRoot(IAccessor currSubject,
      java.util.ArrayList<IAccessor> list, IAccessor root) {
    if (currSubject == null || currSubject == root) {
      return;
    }
    list.add(currSubject);
    if (currSubject instanceof ChildEntityAccessor) {
      getParentAccessorsWithoutRoot(((ChildEntityAccessor) currSubject).getParent(), list, root);
    } else {
      getParentAccessorsWithoutRoot(((ValueObjAccessor) currSubject).getBelongBuffer(), list, root);
    }
  }

  private IChangeDetail getOrCreateParentChangeDetail(IAccessor rootSubject,
      java.util.ArrayList<IAccessor> accessorParentDict) {
    int index = accessorParentDict.size();
    if (context.getChangeDetail() == null) {
      context.setChangeDetail(createModifyChange(rootSubject));
    } else if (context.getChangeDetail().getChangeType() != ChangeType.Modify) {
      return null;
    }

    IChangeDetail tempVar = context.getChangeDetail();
    RefObject<Integer> tempRef_index = new RefObject<Integer>(index);
    IChangeDetail tempVar2 = getOrCreateParentChangeDetail(
        (AbstractModifyChangeDetail) ((tempVar instanceof AbstractModifyChangeDetail) ? tempVar
            : null), accessorParentDict, tempRef_index);
    index = tempRef_index.argvalue;
    return tempVar2;
  }

  private IChangeDetail getOrCreateParentChangeDetail(AbstractModifyChangeDetail parentChangeDetail,
      java.util.ArrayList<IAccessor> accessorParentDict, RefObject<Integer> index) {
    if (index.argvalue == 0) {
      return parentChangeDetail;
    }

    IAccessor currChange = accessorParentDict.get(index.argvalue - 1);
    IChangeDetail changeDetail;
    if (currChange instanceof EntityAccessor) {
      EntityAccessor accessor = (EntityAccessor) ((currChange instanceof EntityAccessor)
          ? currChange : null);
      changeDetail = ((ModifyChangeDetail) parentChangeDetail)
          .getChildChangeDetail(accessor.getNodeCode(), accessor.getID());
      if (changeDetail == null) {
        changeDetail = createModifyChange(accessor);
        ((ModifyChangeDetail) parentChangeDetail)
            .addChildChangeSet(accessor.getNodeCode(), changeDetail);
      } else if (changeDetail.getChangeType() != ChangeType.Modify) {
        return null;
      }
    } else if (currChange instanceof ValueObjAccessor) {

      ValueObjAccessor accessor = (ValueObjAccessor) ((currChange instanceof ValueObjAccessor)
          ? currChange : null);

      Object existingChange = parentChangeDetail.getPropertyChanges().get(accessor.getPropName());
      if (existingChange instanceof ICefData) {
        return null;
      }
      changeDetail = (AbstractModifyChangeDetail) (
          (existingChange instanceof AbstractModifyChangeDetail) ? existingChange : null);
      if (changeDetail == null) {
        changeDetail = new ValueObjModifyChangeDetail();
        parentChangeDetail.setItem(accessor.getPropName(), changeDetail);
      }
    } else {
      throw new IllegalArgumentException();
    }
    index.argvalue--;
    return getOrCreateParentChangeDetail(
        (AbstractModifyChangeDetail) ((changeDetail instanceof AbstractModifyChangeDetail)
            ? changeDetail : null), accessorParentDict, index);
  }

  private AbstractModifyChangeDetail updateChangeToParentChangeDetail(ChildEntityAccessor editingData, IChangeDetail changeDetail) {
    ModifyChangeDetail modifyChangeDetail = (ModifyChangeDetail)changeDetail;
    String nodeCode = editingData.getNodeCode();
    IChangeDetail childChangeDetail = modifyChangeDetail.getChildChangeDetail(nodeCode, (editingData).getID());
    if (childChangeDetail == null) {
      childChangeDetail = createModifyChange(editingData);
      modifyChangeDetail.addChildChangeSet(editingData.getNodeCode(), childChangeDetail);
      return (AbstractModifyChangeDetail)childChangeDetail;
    } else {
      switch (childChangeDetail.getChangeType()) {
        case Added:
        case Deleted:
          return null;
        case Modify:
          return (AbstractModifyChangeDetail)childChangeDetail;
        default:
          throw new CefExceptionBase();
      }
    }
  }

  private AbstractModifyChangeDetail updateChangeToParentChangeDetail(ValueObjAccessor editingData, IChangeDetail changeDetail) {
    AbstractModifyChangeDetail modifyChangeDetail = (AbstractModifyChangeDetail) (
        (changeDetail instanceof AbstractModifyChangeDetail) ? changeDetail : null);
    Object existingValue = modifyChangeDetail.getPropertyChanges().get(editingData.getPropName());
    AbstractModifyChangeDetail existingChangeDetail = (AbstractModifyChangeDetail) ((modifyChangeDetail.getItem(editingData
        .getPropName()) instanceof AbstractModifyChangeDetail) ?
    modifyChangeDetail.getItem(editingData.getPropName()) :null);
    if (existingChangeDetail != null) {
      switch (existingChangeDetail.getChangeType()) {
        case Added:
        case Deleted:
          return null;
        case Modify:
          return existingChangeDetail;
        default:
          throw new CefExceptionBase();
      }
    } else {
      existingChangeDetail = createModifyChange(editingData);
      modifyChangeDetail.setItem(editingData.getPropName(), existingChangeDetail);
      return existingChangeDetail;
    }
  }

  @Override
  public void OnMultiLangChanging(IAccessor editingData, String propertyName, String langCode,
      Object propertyValue, Object orgPropertyValue, IAccessor rootData) {
    if (getIsSuspended() ||
        (!getIsEquivalentValueEffective() && Objects.equals(propertyValue, orgPropertyValue))) {
      return;
    }
    AbstractModifyChangeDetail change = getChangToChangeSet(editingData, rootData);
    if(change != null) {
      change.setMultiLang(propertyName, langCode, propertyValue);
    }
  }
}
